/**
 * Copyright (c) 2018-2022, NXOS Development Team
 * SPDX-License-Identifier: Apache-2.0
 * 
 * Contains: share memory
 * 
 * Change Logs:
 * Date           Author            Notes
 * 2022-07-09     JasonHu           Init
 */

#include <base/sharemem.h>
#include <base/spin.h>
#include <base/malloc.h>
#include <base/page.h>
#include <base/thread.h>
#include <base/string.h>
#define NX_LOG_NAME "shm"
#include <base/log.h>
#include <base/debug.h>

NX_PRIVATE NX_LIST_HEAD(shareMemListHead);
NX_PRIVATE NX_SPIN_DEFINE_UNLOCKED(shareMemLock);

NX_PRIVATE NX_ShareMem * NX_ShareMemSearchByName(const char * name)
{
    NX_ShareMem * shm;
    NX_UArch level;
    NX_SpinLockIRQ(&shareMemLock, &level);
    NX_ListForEachEntry (shm, &shareMemListHead, list)
    {
        if (NX_StrCmp(shm->name, name) == 0)
        {
            NX_SpinUnlockIRQ(&shareMemLock, level);
            return shm;
        }
    }
    NX_SpinUnlockIRQ(&shareMemLock, level);
    return NX_NULL;
}

NX_ShareMem * NX_ShareMemoryCreate(const char * name, NX_Size size, NX_U32 flags)
{
    NX_Size pageCount;
    NX_ShareMem * shm;

    if (!size || (!name && !(flags & NX_SHAREMEM_ANONYMOUS)))
    {
        return NX_NULL;
    }

    pageCount = NX_DIV_ROUND_UP(size, NX_PAGE_SIZE);

    shm = NX_MemAlloc(sizeof(NX_ShareMem));
    if (!shm)
    {
        return NX_NULL;
    }
    
    shm->pageAddr = (NX_Addr)NX_PageAlloc(pageCount);
    if (!shm->pageAddr)
    {
        NX_MemFree(shm);
        return NX_NULL;
    }
    
    if (flags & NX_SHAREMEM_ANONYMOUS) /* anonymous shm */
    {
        NX_StrCopyN(shm->name, ".anonymous", NX_SHAREMEM_NAME_LEN);
    }
    else
    {
        NX_StrCopyN(shm->name, name, NX_SHAREMEM_NAME_LEN);
    }

    shm->size = pageCount * NX_PAGE_SIZE;
    NX_AtomicSet(&shm->reference, 1);
    shm->flags = flags & ~NX_SHAREMEM_CREATE_NEW;

    return shm;
}

NX_ShareMem * NX_ShareMemOpen(const char * name, NX_Size size, NX_U32 flags)
{
    NX_ShareMem * shm;

    if (!size || size > NX_SHAREMEM_MAX_SIZE)
    {
        return NX_NULL;
    }

    if (!(flags & NX_SHAREMEM_ANONYMOUS))
    {
        if (!name)
        {
            return NX_NULL;
        }

        if ((shm = NX_ShareMemSearchByName(name)) != NX_NULL) /* share memory exist */
        {
            if (flags & NX_SHAREMEM_CREATE_NEW) /* share memory must not exist */
            {
                return NX_NULL;
            }
            if (size > shm->size)
            {
                return NX_NULL;
            }
            NX_AtomicInc(&shm->reference);
            return shm; /* return share mem */
        }
    }

    shm = NX_ShareMemoryCreate(name, size, flags);
    if (!shm)
    {
        return NX_NULL;
    }

    if (!(flags & NX_SHAREMEM_ANONYMOUS))
    {
        NX_UArch level;
        NX_SpinLockIRQ(&shareMemLock, &level);
        NX_ListAdd(&shm->list, &shareMemListHead);
        NX_SpinUnlockIRQ(&shareMemLock, level);
    }

    return shm;
}

NX_Error NX_ShareMemClose(NX_ShareMem * shm)
{
    if (!shm)
    {
        return NX_EINVAL;
    }

    if (!shm->pageAddr)
    {
        return NX_EPERM;
    }
    
    NX_AtomicDec(&shm->reference);

    if (NX_AtomicGet(&shm->reference) > 0) /* this close success */
    {
        return NX_EOK;
    }

    if (!(shm->flags & NX_SHAREMEM_ANONYMOUS))
    {
        NX_UArch level;
        NX_SpinLockIRQ(&shareMemLock, &level);
        NX_ListDel(&shm->list);
        NX_SpinUnlockIRQ(&shareMemLock, level);
    }

    NX_PageFree((void *)shm->pageAddr);
    shm->pageAddr = 0;
    shm->flags = 0;
    NX_MemFree(shm);
    return NX_EOK;
}

NX_Error NX_ShareMemMapToVmspace(NX_ShareMem * shm, NX_Vmspace * space, void ** outMapAddr)
{
    void * mapAddr = NX_NULL;
    NX_Error err;

    if (!shm || !space)
    {
        return NX_EINVAL;
    }

    if (!shm->pageAddr)
    {
        return NX_EPERM;
    }

    if ((err = NX_VmspaceMapWithPhy(space, 0, shm->pageAddr, shm->size, NX_PAGE_ATTR_USER, NX_VMSPACE_SHAREMEM, &mapAddr)) != NX_EOK)
    {
        return err;
    }

    if (outMapAddr)
    {
        *outMapAddr = mapAddr;
    }

    return NX_EOK;
}

NX_Error NX_ShareMemMap(NX_ShareMem * shm, void ** outMapAddr)
{
    NX_Thread * self;
    NX_Process * process;

    self = NX_ThreadSelf();
    process = NX_ThreadGetProcess(self);
    
    if (process == NX_NULL)
    {
        return NX_EPERM;
    }

    return NX_ShareMemMapToVmspace(shm, &process->vmspace, outMapAddr);
}

NX_Error NX_ShareMemUnmapFromVmspace(NX_ShareMem * shm, NX_Vmspace * space, void * vaddr)
{
    if (!vaddr || !shm || !space)
    {
        return NX_EINVAL;
    }

    /* vaddr must page aligned */
    NX_ASSERT(!((NX_Addr)vaddr & NX_PAGE_MASK));

    /* unmap space */
    return NX_VmspaceUnmap(space, (NX_Addr)vaddr, shm->size, NX_VMSPACE_SHAREMEM);
}

NX_Error NX_ShareMemUnmap(NX_ShareMem * shm, void * addr)
{
    NX_Addr vaddr;
    NX_Thread * self;
    NX_Process * process;

    if (!addr)
    {
        return NX_EINVAL;
    }

    self = NX_ThreadSelf();
    process = NX_ThreadGetProcess(self);

    if (process == NX_NULL)
    {
        return NX_EPERM;
    }

    vaddr = ((NX_Addr)addr) & NX_PAGE_ADDR_MASK;
    return NX_ShareMemUnmapFromVmspace(shm, &process->vmspace, (void *)vaddr);
}
